package intelligentcity.model.traffic;

import intelligentcity.conf.Conf;
import intelligentcity.model.City;
import intelligentcity.model.Move;
import intelligentcity.model.Point;
import intelligentcity.model.Road;
import intelligentcity.model.intersections.Intersection;
import intelligentcity.model.streets.OneWayStreet;
import intelligentcity.model.streets.Street;
import intelligentcity.model.turns.Turn;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;

public class Car extends Thread {

    private final static Random random = new Random();

    private final City city;
    private final Point point;
    private final Traffic traffic;
    private final int id;

    private IBehaviour behaviour;
    private AtomicBoolean willingToTurnLeft;
    private Road currentRoad;
    private OneWayStreet currentOWS;

    public Car(City city, Traffic traffic, int id) {

        this.city = city;
        this.traffic = traffic;
        this.id = id;
        this.point = city.getRandomStartPoint();
        this.willingToTurnLeft = new AtomicBoolean(false);

        boolean streetSelected = false;
        streetSelected: if (!streetSelected) {
            for (Street street : city.getStreets()) {
                for (OneWayStreet ows : street.getOneWayStreets()) {
                    if (ows.contains(point)) {
                        currentOWS = ows;
                        currentRoad = street;
                        streetSelected = true;
                        break streetSelected;
                    }
                }
            }
        }
        setBehaviour();
    }

    @Override
    public void run() {
        traffic.getTimeCounter().logPresence(id);
        while (point.getX() >= city.getLeftborderoftraffic() && point.getY() >= city.getUpperborderoftraffic()
                && point.getX() <= city.getRightborderoftraffic() && point.getY() <= city.getLowerborderoftraffic()) {
            go();
        }
        traffic.getTimeCounter().logAbsence(id);
        traffic.getCars().remove(id);
    }

    private void setBehaviour() {
        if (currentRoad instanceof Intersection) {
            behaviour = new BehaviourOnIntersection(this);
        } else if (currentRoad instanceof Street) {
            behaviour = new BehaviourOnStreet(this);
        } else if (currentRoad instanceof Turn) {
            behaviour = new BehaviourOnTurn(this);
        }

    }

    private void go() {

        Conf.stop(Conf.carStopTime);

        if (!behaviour.canIMove(currentOWS.getMove(), Conf.distanceBetweenCars)) {
            return;
        }
        behaviour.behave();

        if (currentOWS.getMove() == Move.UP) {
            if (currentOWS.contains(point.getX(), point.getY() - 1)) {
                point.decY();
            } else {
                changeRoad();
            }

        }
        if (currentOWS.getMove() == Move.DOWN) {
            if (currentOWS.contains(point.getX(), point.getY() + 1)) {
                point.incY();
            } else {
                changeRoad();
            }
        }
        if (currentOWS.getMove() == Move.LEFT) {
            if (currentOWS.contains(point.getX() - 1, point.getY())) {
                point.decX();
            } else {
                changeRoad();
            }
        }
        if (currentOWS.getMove() == Move.RIGHT) {
            if (currentOWS.contains(point.getX() + 1, point.getY())) {
                point.incX();
            } else {
                changeRoad();
            }
        }

    }

    private void changeRoad() {
        List<Road> roads = new ArrayList<>();
        for (Road r : currentRoad.getNeighbors()) {
            for (OneWayStreet ows : r.getOneWayStreets()) {
                if (ows.contains(point)) {
                    roads.add(r);
                }
            }
        }
        if (roads.size() > 0) {
            currentRoad = roads.get(random.nextInt(roads.size()));
            for (OneWayStreet ows : currentRoad.getOneWayStreets()) {
                if (ows.contains(point)) {
                    currentOWS = ows;
                    break;
                }
            }
            setBehaviour();
        }

    }

    public Point getPoint() {
        return point;
    }

    public City getCity() {
        return city;
    }

    public OneWayStreet getCurrentOWS() {
        return currentOWS;
    }

    public void setCurrentOWS(OneWayStreet currentOWS) {
        this.currentOWS = currentOWS;
    }

    public Traffic getTraffic() {
        return traffic;
    }

    public Road getCurrentRoad() {
        return currentRoad;
    }

    public void setCurrentRoad(Road currentRoad) {
        this.currentRoad = currentRoad;
    }

    public boolean isWillingToTurnLeft() {
        return willingToTurnLeft.get();
    }

    public void setWillingToTurnLeft(boolean isWilling) {
        this.willingToTurnLeft.set(isWilling);
    }

}
